using System;
using System.Collections.Generic;
using System.Linq;
using Roslyn.Compilers.CSharp;

namespace ContentOverContainer.Common.Converter
{
    public static class MemberAccessAndInvocationconverter
    {
        /// <summary>
        /// Converts the member access expression. R-MembAcc2
        /// </summary>
        /// <param name="pExprSyntax">The expression syntax.</param>
        /// <param name="pConverterData">The converter data.</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentException">left and/or right side of member access expression resulted in null</exception>
        /// <author>
        /// Harald Binkle
        /// </author>
        public static ChangeNodeInfo ConvertMemberAccessExpression(MemberAccessExpressionSyntax pExprSyntax, ConverterContext pConverterData)
        {
            if (pExprSyntax.Name.EquivalentTo(pConverterData.NodeTypeInfo.Node))//the annnotated member is the target of the member access, so we don't need to do anything
                return new ChangeNodeInfo(pExprSyntax);

            var declNode = pExprSyntax.Name.GetMemberDeclaration(pConverterData.SemanticModel);
            if (declNode == null) //case it's a call to a member not defined in this solution
                return new ChangeNodeInfo(pExprSyntax);

            InvocationExpressionSyntax invSyntax = null;
            bool isMethodInvocation = false;
            if (declNode is MethodDeclarationSyntax)
            {
                invSyntax = (InvocationExpressionSyntax)pExprSyntax.Parent;
                isMethodInvocation = true;                
            }                       
            //check the multiplicities of left and right side node
            var leftSide = pExprSyntax.Expression.FirstAncestorOrSelf<SimpleNameSyntax>();
            var rightSide = pExprSyntax.Name.FirstAncestorOrSelf<SimpleNameSyntax>();
            if (leftSide != null && rightSide != null)
            {
                var leftDecl = leftSide.GetMemberDeclaration(pConverterData.SemanticModel);
                var righDdecl = rightSide.GetMemberDeclaration(pConverterData.SemanticModel);
                var leftSideMultKind = leftDecl.GetMultiplicityKind();
                var rightSideMultKind = righDdecl.GetMultiplicityKind();

                //R-MembAcc1 (e.d; e=option)
                if (leftSideMultKind == MultiplicityKind.Option)
                {
                    ExpressionSyntax optionReplacement;
                    string replacementStr = String.Format("({0} == null ? null : {0})", pExprSyntax.Expression);
                    if (isMethodInvocation)
                    {
                        replacementStr = invSyntax.ToString().Replace(leftSide.ToString(), replacementStr);
                        optionReplacement = Syntax.ParseExpression(replacementStr);
                    }
                    else optionReplacement = Syntax.ParseExpression(replacementStr);
                    return new ChangeNodeInfo(optionReplacement);
                }

                //--> left side is any

                var resultListType = righDdecl.GetDeclarationType();

                var rightSideString = rightSide.ToString();
                if (isMethodInvocation)
                    rightSideString += invSyntax.ArgumentList.ToString();

                var parameter = new List<ParameterPair>();

                TypeSyntax type;
                if (pConverterData.MultiplicityInfo.Kind == MultiplicityKind.Any)
                    type = pConverterData.MultiplicityInfo.Type;
                else
                    type = righDdecl.GetDeclarationType();//this case should never occur!!!

                parameter.Add(new ParameterPair() { Type = type, Name = leftSide });
                if (isMethodInvocation)
                {
                    var argsTypes = ExpressionSyntaxConverter.ResolveArgumentListTypes(declNode as MethodDeclarationSyntax);
                    for (int i = 0; i < invSyntax.ArgumentList.Arguments.Count(); i++)
                    {
                        parameter.Add(new ParameterPair()
                            {
                                Type = argsTypes[i].Type,
                                Name = invSyntax.ArgumentList.Arguments[i].Expression.FirstAncestorOrSelf<ExpressionSyntax>()
                            });
                    }
                }
                //R-MembAcc2 (e.d e=any, d<any) 
                if (rightSideMultKind != MultiplicityKind.Any)
                {
                    //introduce an additional variable for avoiding property getter side effects when calling the getter twice
                    string tempResultName = "resList" + MethodComposer.RandomVarialbleName();
                    var methodBody = (BlockSyntax)Syntax.ParseStatement(
                        String.Format("{{//" + pExprSyntax.ToString() + Environment.NewLine +
                                      "var {3} = new List<{0}>();" + Environment.NewLine +
                                      "foreach(var temp in {1})" + Environment.NewLine +
                                      "{{ " + Environment.NewLine +
                                      "  var tempValue = temp.{2};" + Environment.NewLine +
                                      "  if(tempValue != null) " + Environment.NewLine +
                                      "    {3}.Add(tempValue);" + Environment.NewLine +
                                      "}}" + Environment.NewLine +
                                      "return {3};" +
                                      "}}" + Environment.NewLine
                                      , resultListType, leftSide, rightSideString, tempResultName));

                    var composition = MethodComposer.BuildMethod("MemAcc2", methodBody, parameter, String.Format("List<{0}>", resultListType));

                    var result = new ChangeNodeInfo(composition.InvocationCall);
                     var methodSubstitution = new ChangeNodeMethodSubstitution()
                    {
                        Class = pExprSyntax.FirstAncestorOrSelf<ClassDeclarationSyntax>(),
                        MethodDeclaration = composition.MethodDecl
                    };
                    result.MethodSubstitutionList.Add(methodSubstitution);
                    return result;
                }
                else//R-MembAcc3 (e.d e=any, d=any)
                {
                    string tempResultName = "resList" + MethodComposer.RandomVarialbleName();
                    var methodBody = (BlockSyntax)Syntax.ParseStatement(
                        String.Format("{{//" + pExprSyntax.ToString() + Environment.NewLine +
                                      "var {3} = new List<{0}>();" + Environment.NewLine +
                                      "foreach(var temp in {1})" + Environment.NewLine +
                                      "{{ " + Environment.NewLine +
                                      "  {3}.AddRange(temp.{2});" + Environment.NewLine +
                                      "}}" + Environment.NewLine +
                                      "return {3};" +
                                      "}}" + Environment.NewLine
                                      , resultListType, leftSide, rightSideString, tempResultName));

                    var composition = MethodComposer.BuildMethod("MemAcc3", methodBody, parameter, String.Format("List<{0}>", resultListType));

                    var result = new ChangeNodeInfo(composition.InvocationCall);
                    var methodSubstitution = new ChangeNodeMethodSubstitution()
                    {
                        Class = pExprSyntax.FirstAncestorOrSelf<ClassDeclarationSyntax>(),
                        MethodDeclaration = composition.MethodDecl
                    };
                    result.MethodSubstitutionList.Add(methodSubstitution);
                    return result;
                }
            }
            throw new ArgumentException("left and/or right side of member access expression resulted in null");
        }
    }
}